package com.ylr.jwt.framework.service.impl;

import com.ylr.jwt.framework.properties.JwtProperties;
import com.ylr.jwt.framework.service.JwtService;
import com.ylr.jwt.common.enums.JwtResultEnum;
import com.ylr.jwt.common.exception.CheckJwtException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2023-04-26 10:14:58
 * className: JwtServiceImpl
 * version: 1.0
 * description:
 */
@Service("jwtService")
public class JwtServiceImpl implements JwtService {

    private static final Logger log = LoggerFactory.getLogger(JwtServiceImpl.class);

    /**
     * 服务名称
     */
    private static final String SUBJECT = "user-token";
    /**
     * 服务名称
     */
    private static final String SERVICE_NAME = "YLR";

    @Resource(name = "jwtProperties")
    private JwtProperties jwtProperties;

    @Override
    public String createWebToken(String userId) {

        JwtProperties.Config web = jwtProperties.getWeb();
        if (Objects.isNull(web)) {
            log.error("jwt web配置为空，jwt加密失败，请确认");
            throw new CheckJwtException(JwtResultEnum.PROP_LOSE);
        }

        return createToken(userId, web.getSecret(), web.getExpire());
    }

    @Override
    public String checkWebToken(String token) {
        JwtProperties.Config web = jwtProperties.getWeb();
        if (Objects.isNull(web)) {
            log.error("jwt web配置为空，jwt解密失败，请确认");
            throw new CheckJwtException(JwtResultEnum.PROP_LOSE);
        }
        // token验证流程
        try {
            // 解析token内容
            Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(key(web.getSecret())).build().parseClaimsJws(token);
            // 获取token中携带数据
            Claims body = claimsJws.getBody();

            return body.get("userId", String.class);
        } catch (ExpiredJwtException e) {
            // token签名到期
            log.error("token签名到期。token={} message={}", token, e.getMessage(), e);
            throw new CheckJwtException(JwtResultEnum.EXPIRE);
        } catch (SignatureException e) {
            // token签名被修改
            log.error("token签名被修改。token={} message={}", token, e.getMessage(), e);
            throw new CheckJwtException(JwtResultEnum.SIGNATURE);
        } catch (MalformedJwtException e) {
            // 不是token字符串
            log.error("不是token字符串。token={} message={}", token, e.getMessage(), e);
            throw new CheckJwtException(JwtResultEnum.MALFORMED);
        } catch (Exception e) {
            // 不是已知异常
            log.error("不是已知异常。token={} class={} message={}", token, e.getClass(), e.getMessage(), e);
            throw e;
        }
    }

    private String createToken(String userId, String secret, Long expire) {
        Date issuedAt = new Date();
        Date expireDate = new Date(issuedAt.getTime() + expire);

        Map<String, Object> map = new HashMap<>(8);
        // 设置数据加密算法和令牌类型
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        /*
            设置令牌的头部信息、签名的主题、载荷、生成签名的时间、签名的过期时间、令牌携带的信息和令牌加密方式
            并构造出令牌信息
         */
        // 构建jwt字符串
        return Jwts.builder()
                // 设置头部信息 Header
                .setHeader(map)
                // 签名的主题
                .setSubject(SUBJECT)
                // 设置 载荷 签名是有谁生成 例如 服务器
                .setIssuer(SERVICE_NAME)
                // 设置生成签名的时间
                .setIssuedAt(issuedAt)
                // 设置签名过期时间
                .setExpiration(expireDate)
                // 设置token携带信息
                .claim("userId", userId)
                // 设置token加密方式
                .signWith(key((secret)), SignatureAlgorithm.HS256)
                // 构造token
                .compact();
    }

    /**
     * 生成加密Key
     * @param secret 密钥
     * @return 加密Key对象
     */
    private SecretKey key(String secret) {
        if (!StringUtils.hasLength(secret)) {
            log.error("jwt web secret配置为空，请确认");
            throw new CheckJwtException(JwtResultEnum.PROP_LOSE);
        }
        return Keys.hmacShaKeyFor(Base64.getEncoder().encode(secret.getBytes(StandardCharsets.UTF_8)));
    }
}
